#ifndef __MY_UTIL__
#define __MY_UTIL__

#include <iostream>
#include <fstream>
#include <string>
#include <unistd.h>
#include <vector>

#include <jsoncpp/json/json.h>
#include <experimental/filesystem> //C++17
#include <sys/types.h>
#include <sys/stat.h>

#include "bundle.h"

namespace cloud
{
    namespace fs = std::experimental::filesystem;
    class FileUtil
    {
    private:
        std::string _filename; // 文件名称
    public:
        bool Remove()
        {
            if (this->Exists() == false)
            {
                return true;
            }
            remove(_filename.c_str());
            return true;
        }

        FileUtil(const std::string &filename)
            : _filename(filename)
        {
        }

        // 获取文件大小(老师用int64_t)
        size_t FileSize()
        {
            struct stat st;
            // 使用stat函数传入文件名和st的地址
            if (stat(_filename.c_str(), &st) < 0)
            {
                // 获取失败
                std::cout << "get file size failed!\n";
                return -1;
            }
            return st.st_size; // 返回文件大小(字节)
        }

        // 获取文件最后一次访问时间
        time_t LastATime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get LastATime failed!\n";
                return -1;
            }
            // 返回最后一次访问时间
            return st.st_atime;
        }

        // 获取文件最后一次修改时间
        time_t LastMTime()
        {
            struct stat st;
            if (stat(_filename.c_str(), &st) < 0)
            {
                std::cout << "get LastMTime failed!\n";
                return -1;
            }
            return st.st_mtime;
        }
        // 获取文件路径中的文件名称-服务器管理的文件不需要文件路径；如_filenmae=./a/c/t.txt我们只需要t.txt即可
        std::string FileName()
        {
            // 获取文件名称只需要找到最后一个/符号即可
            size_t pos = _filename.find_last_of("/");
            if (pos == std::string::npos)
            {
                // 没有找到/，说明本身就是文件名称
                return _filename;
            }
            // 返回/之后的字符，进行字符串截断，从/的下一个位置开始直到结束
            return _filename.substr(pos + 1);
        }

        //------------------------------------------------------------

        // read 读取文件指定位置开始指定长度的数据-用于后续支持断点续传-（参数body用来保存读取的数据！）
        bool GetPosLen(std::string *body, size_t pos, size_t len)
        {
            // 获取文件大小
            size_t fsize = this->FileSize();
            // 判断从指定位置开始的数据长度是否有误
            if (pos + len > fsize)
            {
                std::cout << "get file len is error!\n";
                return false;
            }
            // 从文件里面读取数据
            std::ifstream ifs;
            // 以二进制形式打开当前文件
            ifs.open(_filename, std::ios::binary);
            // 判断文件是否打开成功
            if (ifs.is_open() == false)
            {
                std::cout << "read open file failed!\n";
                return -1;
            }
            // 从文件起始位置开始偏移，偏移到pos位置
            ifs.seekg(pos, std::ios::beg);
            // 偏移之后修改body大小为len大小（因为数据要存储在body指针中！）
            body->resize(len);
            // 调整之后读取文件数据到body中,body此时是指针，所以要先解引用
            ifs.read(&(*body)[0], len);
            // 判断上面操作是否正常
            if (ifs.good() == false)
            {
                std::cout << "get file content failed!\n";
                ifs.close();
                return false;
            }
            ifs.close();
            return true;
        }
        // 读取文件的所有数据将文件数据放到body中
        bool GetContent(std::string *body)
        {
            // 此时调用GetPosLen函数，此时len的大小为文件大小
            GetPosLen(body, 0, this->FileSize());
        }

        // 将body中的数据写入到文件中-参数body中保存的就是要写入文件中的数据
        bool SetContent(const std::string &body)
        {
            std::ofstream ofs;
            // 二进制形式打开文件
            ofs.open(_filename, std::ios::binary);
            if (ofs.is_open() == false)
            {
                std::cout << "write open file failed!\n";
                return false;
            }
            // 打开成功之后写入数据
            ofs.write(&body[0], body.size());
            if (ofs.good() == false)
            {
                std::cout << "write open file failed!\n";
                ofs.close();
                return false;
            }
            ofs.close(); // 关闭文件
            return true;
        }

        //------------------------------------------------

        // 文件的压缩-参数是压缩包名称，把压缩之后的数据放入压缩包文件中
        bool Compress(const std::string &packname)
        {
            // 1.获取源文件数据
            std::string body;
            if (this->GetContent(&body) == false)
            {
                std::cout << "compress get file  content failed!\n";
                return false;
            }
            // 2.对数据进行压缩
            std::string packed = bundle::pack(bundle::LZIP, body);

            // 3.将压缩的数据存储在压缩包文件中
            FileUtil fu(packname);
            if (fu.SetContent(packed) == false)
            {
                std::cout << "compress write  file  content failed!\n";
                return false;
            }
            return true;
        }
        // 文件的解压缩-参数是解压缩之后的文件名称-把解压后的数据放入该文件中
        bool UnCompress(const std::string &unpackname)
        {
            // 1.将当前压缩包文件读取出来
            std::string body;
            if (this->GetContent(&body) == false)
            {
                std::cout << "compress set file  content failed!\n";
                return false;
            }
            // 2.对数据进行压缩
            std::string unpacked = bundle::unpack(body);

            // 3.将压缩的数据存储在压缩包文件中
            FileUtil fu(unpackname);
            if (fu.SetContent(unpacked) == false)
            {
                std::cout << "uncompress write  file  content failed!\n";
                return false;
            }
            return true;
        }

        //------------------------------------------------

        // 判断当前文件是否存在
        bool Exists()
        {
            return fs::exists(_filename);
        }
        // 创建文件目录，创建的文件名称就是成员变量_filename
        bool CreateDirectory()
        {
            if (this->Exists())
                return true;
            return fs::create_directories(_filename);
        }
        // 遍历文件夹中所有的文件，将其放在array数组中
        bool ScanDirectory(std::vector<std::string> *array)
        {
            // 通过调用函数directory_iterator遍历
            for (auto &p : fs::directory_iterator(_filename))
            {
                // 还要进行判断是否是普通文件，因为目录下还可能存在目录，不能将目录传上去
                // 使用接口is_directory函数来判断是否是一个目录
                if (fs::is_directory(p) == true)
                {
                    // 如果是一个目录就跳过
                    continue;
                }
                // 如果不是目录就将其添加到数组中,注意p不是string类对象，所以不能直接添加
                //  要使用这个path先来进行实例化，然后调用对应的函数，数组中保存的是带路径的文件名
                //  所以可以使用relative_path,获取相对路径，返回值也是path对象，然后调用string()
                //  获取的就是带路径名的string对象
                array->push_back(fs::path(p).relative_path().string());
            }
            return true;
        }
    };

    class JsonUtil
    {
    public:
        // 序列化就是将Json::Value中的数据转换成为json格式的字符串
        static bool Serialize(const Json::Value &root, std::string *str)
        {
            Json::StreamWriterBuilder swb;
            std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
            // 调用write将root中的内容写入到字符串流中
            std::stringstream ss;
            // sw的write方法将root（一个Json::Value对象，包含要序列化的JSON数据）写入到std::stringstream对象ss中
            if (sw->write(root, &ss) != 0)
            {
                std::cout << "json write failed!\n";
                return false;
            }
            *str = ss.str();
            return true;
        }

        // 反序列化就是将json格式的字符串放入到Json::Value对象中
        static bool UnSerialize(const std::string &str, Json::Value *root)
        {
            Json::CharReaderBuilder crb;
            std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
            // 用来记录错误信息
            std::string err;
            // parse四个参数：开始指针，结束指针，存储位置，错误采集
            if (cr->parse(str.c_str(), str.c_str() + str.size(), root, &err) == false)
            {
                std::cout << "prase error:" << err << std::endl;
                return false;
            }
            return true;
        }
    };

}

#endif